﻿using FakeItEasy;
using Microsoft.Owin.Testing;
using Owin;
using AutoFixture;
using SimpleInjector;
using Sustainalytics.Auth.Common;
using Sustainalytics.Auth.Service.GA;
using Sustainalytics.Auth.Service.OAuth;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;

namespace Sustainalytics.Auth.Service.Tests
{
    public class TokenServer : IDisposable
    {
        private readonly Container container;
        private readonly TestServer server;

        public TokenServer()
        {
            container = new AutoMockingContainer();

            container.Register<CustomAuthorizationServerProvider>();
            container.Register<CustomRefreshTokenProvider>();

            var oAuthServerOptions = Startup.GetOAuthServerOptions(container, TimeSpan.FromHours(1), true);
            server = TestServer.Create(app => app.UseOAuthAuthorizationServer(oAuthServerOptions));
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                container?.Dispose();
                server?.Dispose();
            }
        }

        public void SetFakeGaUser(string username, GaUser gaUser)
        {
            var gaUserRepository = container.GetInstance<GaUserRepository>();
            A.CallTo(() => gaUserRepository.GetByUsernameAsync(username)).Returns(gaUser).Once();
        }

        public static GaUser CreateActiveGaUser(string username, string password)
        {
            var fixture = new Fixture();
            var gaUser = fixture.Create<GaUser>();

            gaUser.Email = username;
            gaUser.Username = username;
            gaUser.Password = PasswordManager.HashPassword(password);
            gaUser.PasswordExpirationDate = DateTime.UtcNow.AddDays(1);
            gaUser.IsLegalAgreementAccepted = true;

            return gaUser;
        }

        public async Task<HttpResponseMessage> GetResponseAsync(IEnumerable<KeyValuePair<string, string>> content)
        {
            return await server.CreateRequest("/token").And(x => x.Content = new FormUrlEncodedContent(content)).PostAsync();
        }

        public async Task<HttpResponseMessage> GetReponseByPasswordAsync(string username = null, string password = null,
            string clientId = "clientId1", string clientSecret = "secret1")
        {
            const string passwordGrantType = "password";
            var content = new Dictionary<string, string>
            {
                { "grant_type",    passwordGrantType },
                { "username",      username          },
                { "password",      password          },
                { "client_id",     clientId          },
                { "client_secret", clientSecret      },
            };

            return await GetResponseAsync(content);
        }

        public async Task<HttpResponseMessage> GetReponseByRefreshTokenAsync(string clientId, string refreshToken)
        {
            const string refreshTokenGrantType = "refresh_token";
            var content = new Dictionary<string, string>
            {
                { "grant_type",    refreshTokenGrantType },
                { "client_id",     clientId              },
                { "refresh_token", refreshToken          },
            };

            return await GetResponseAsync(content);
        }

        public async Task<string> GetRefreshTokenAsync(string username = null, string password = null)
        {
            SetupFakeRefreshTokenRepository();

            var response = await GetReponseByPasswordAsync(username, password);
            var tokenResponse = await response.Content.ReadAsAsync<TokenResponse>();

            return tokenResponse.RefreshToken;
        }

        private void SetupFakeRefreshTokenRepository()
        {
            var refreshTokenRepository = container.GetInstance<RefreshTokenRepository>();
            RefreshToken refreshToken = null;
            A.CallTo(() => refreshTokenRepository.AddAsync(A<RefreshToken>.Ignored))
                .Invokes((RefreshToken x) => { refreshToken = x; }) // Capture the refreshToken parameter
                .Returns(Task.FromResult(0)); // Must return a finished Task
            A.CallTo(() => refreshTokenRepository.GetByIdAsync(A<string>.That.Matches(x => x == refreshToken.Id.ToString("n"))))
                .ReturnsLazily(() => refreshToken); // Return refreshToken lazily because it's value is set when the call to get the refresh token is made
        }
    }
}
